package miniproj.services.xml;

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.io.StringWriter;
import java.text.SimpleDateFormat;
import java.util.LinkedList;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.xpath.*;

import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import miniproj.config.Config;
import miniproj.models.people.Person;
import miniproj.models.reservation.Date;
import miniproj.models.reservation.Reservation;
import miniproj.models.reservation.Schedule;
import miniproj.models.groups.Group;

/**
 * This class can save in a HTML file a list of Reservation using the layout
 * stored in the HTML model file. (TimeTable layout)
 * 
 * @author William TASSOUX
 * 
 */
public class HtmlDisplay {

	private Document documentModel;

	/**
	 * Instantiates a new html display.
	 * 
	 */
	public HtmlDisplay() throws ParserConfigurationException, SAXException,
			IOException, XPathExpressionException {
		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
		factory.setFeature(
				"http://apache.org/xml/features/nonvalidating/load-external-dtd",
				false);
		DocumentBuilder parser = factory.newDocumentBuilder();

		documentModel = parser.parse(Config.HTML_FOLDER_PATH + "/"
				+ Config.HTML_MODEL_PATH);
		removeEmptyNodes();
	}

	/**
	 * Generate html time table.
	 * 
	 * @param p
	 *            the person for who the time table will be generated
	 * @param l
	 *            the list of reservations
	 * @return the HTML content generated
	 */
	public String generateHtmlTimetable(Person p, LinkedList<Reservation> l)
			throws XPathExpressionException, ParserConfigurationException,
			TransformerException, IOException {
		return this.generateHtmlTimetable(
				p.getFirstName() + " " + p.getLastName(), l);
	}

	/**
	 * Generate html time table.
	 * 
	 * @param g
	 *            the group for which the time table will be generated
	 * @param l
	 *            the list of reservations
	 * @return the HTML content generated
	 */
	public String generateHtmlTimetable(Group g, LinkedList<Reservation> l)
			throws XPathExpressionException, ParserConfigurationException,
			TransformerException, IOException {
		return this.generateHtmlTimetable(g.getId(), l);
	}

	/**
	 * Generate html time table.
	 * 
	 * @param header
	 *            the header of the time table
	 * @param l
	 *            the list of reservations
	 * @return the HTML content generated
	 */
	public String generateHtmlTimetable(String header, LinkedList<Reservation> l)
			throws XPathExpressionException, ParserConfigurationException,
			TransformerException, IOException {
		Document tmpDoc;
		Node curDateTableNode, mainContainer, dateTitleModel, tableModel, tableRowModel, tmpNode;
		Date curDate;
		LinkedList<Reservation> sortedReservations;

		curDate = null;
		sortedReservations = Reservation.orderByDate(l);
		tmpDoc = getDocumentCopy();
		mainContainer = getNodeFromType(tmpDoc, "id", "main-container");
		tmpNode = getNodeFromType(tmpDoc, "id", "main-title");
		dateTitleModel = getNodeFromType(tmpDoc, "class", "date-title");
		tableModel = getNodeFromType(tmpDoc, "class", "time-table");
		tableRowModel = getNodeFromType(tmpDoc, "class", "time-slot");
		if (tableRowModel.getChildNodes().getLength() != 4) {
			throw new ParserConfigurationException(
					"The table row Model is invalid");
		}
		tmpNode.setTextContent(header);
		curDateTableNode = null;
		for (Reservation res : sortedReservations) {
			if (curDate == null || !res.getDate().dayEquals(curDate)) {
				if (curDateTableNode != null) {
					mainContainer.appendChild(curDateTableNode);
				}
				curDate = res.getDate();
				tmpNode = dateTitleModel.cloneNode(true);
				tmpNode.setTextContent(res.getDate().toStringHtml());
				mainContainer.appendChild(tmpNode);
				curDateTableNode = tableModel.cloneNode(false);
			}
			tmpNode = tableRowModel.cloneNode(true);
			tmpNode.getChildNodes().item(0)
					.setTextContent(res.getTeaching().getName());
			tmpNode.getChildNodes().item(1)
					.setTextContent(res.getRoom().getId().toString());
			tmpNode.getChildNodes()
					.item(2)
					.setTextContent(
							scheduleToString(res.getDate().getSchedule()));
			tmpNode.getChildNodes().item(3)
					.setTextContent(res.getClient().toString());
			curDateTableNode.appendChild(tmpNode);
		}

		if (curDateTableNode != null)
			mainContainer.appendChild(curDateTableNode);

		return writeDocumentToFile(tmpDoc);
	}

	private String scheduleToString(Schedule schedule) {
		switch (schedule) {
		case Morning1:
			return "08H00 - 10H00";
		case Morning2:
			return "10H15 - 12H15";
		case Afternoon1:
			return "13H30 - 15H30";
		case Afternoon2:
			return "15H45 - 17H45";
		default:
			return "";
		}
	}

	private Document getDocumentCopy() throws ParserConfigurationException {
		Node originalRoot = documentModel.getDocumentElement();
		DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
		DocumentBuilder db = dbf.newDocumentBuilder();

		Document copy = db.newDocument();
		Node copiedRoot = copy.importNode(originalRoot, true);
		copy.appendChild(copiedRoot);

		return copy;
	}

	private Node getNodeFromType(Document doc, String type, String id)
			throws XPathExpressionException {
		XPathFactory xPathfactory = XPathFactory.newInstance();
		XPath xpath = xPathfactory.newXPath();
		XPathExpression expr = xpath.compile("//*[@" + type + "='" + id + "']");
		NodeList nodeList = (NodeList) expr.evaluate(doc,
				XPathConstants.NODESET);
		if (nodeList.getLength() != 1) {
			throw new IllegalArgumentException("Error: Bad parameters");
		}
		return (nodeList.item(0));
	}

	private void removeEmptyNodes() throws XPathExpressionException {
		XPathFactory xpathFactory = XPathFactory.newInstance();
		XPathExpression xpathExp = xpathFactory.newXPath().compile(
				"//text()[normalize-space(.) = '']");
		NodeList emptyTextNodes = (NodeList) xpathExp.evaluate(documentModel,
				XPathConstants.NODESET);

		for (int i = 0; i < emptyTextNodes.getLength(); i++) {
			Node emptyTextNode = emptyTextNodes.item(i);
			emptyTextNode.getParentNode().removeChild(emptyTextNode);
		}
	}

	private String writeDocumentToFile(Document doc)
			throws TransformerException, IOException {
		SimpleDateFormat sdfDate = new SimpleDateFormat("yyyy-MM-dd HH-mm-ss");
		java.util.Date now = new java.util.Date();
		String data = documentToString(doc);
		String filename = "EDT - " + sdfDate.format(now) + ".html";
		FileWriter fw = new FileWriter(filename, true);
		BufferedWriter output = new BufferedWriter(fw);

		output.write(data);
		output.flush();
		output.close();
		return filename;
	}

	private String documentToString(Document doc) throws TransformerException {
		TransformerFactory transFactory = TransformerFactory.newInstance();
		Transformer transformer;

		transformer = transFactory.newTransformer();
		StringWriter buffer = new StringWriter();
		transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
		if (doc.getChildNodes().getLength() != 0)
			transformer.transform(new DOMSource(doc.getChildNodes().item(0)),
					new StreamResult(buffer));

		return (buffer.toString());
	}
}
